#include "stdafx.h"
#include <vcclr.h>
#include "problem_solver_core.h"
#include "problem.h"
#include "solver.h"
#include "MLP.h"
#include "Solver Form.h"
#include "Input.h"
#include "Output.h"
#include "Target.h"

namespace nnse
{
	extern io_module io_m;

	// Constructor and Destructor

	template <class T, class U> solver<T, U>::solver(problem<T, U>^ p, System::String^ t)
	: parent(p)
	{
		io_m.startup_stream << "Constructing solver" << std::endl;
		put_title(t);
		get_parent_problem()->put_training_flag();
		get_parent_problem()->put_input_flag();
		get_parent_problem()->put_current_training_example(0);
		get_parent_problem()->put_current_testing_example(0);
		allocate_storage();
		put_number_of_MLPs(1);
		put_MLPs();
		solver_form=gcnew SimpleGUI::Solver<T, U>(this);
	}

	template <class T, class U> solver<T, U>::~solver()
	{
		io_m.startup_stream << "Destructing solver" << std::endl;
	}

	// Accessor Functions

	template <class T, class U> inline problem<T, U>^ solver<T, U>::get_parent_problem()
	{
		return parent;
	}

	template <class T, class U> inline System::String^ solver<T, U>::get_title()
	{
		return gcnew System::String(title);
	}

	template <class T, class U> inline void solver<T, U>::put_title(System::String^ t)
	{
		title=gcnew System::String(t);
	}

	template <class T, class U> inline U solver<T, U>::get_training_output_value(unsigned int o_n)
	{
		if (training_output_vector && o_n>=0 && o_n<training_output_vector->Length)
		{
			return training_output_vector[o_n];
		}
		else
		{
			throw std::out_of_range("\nError in solver<T, U>::get_training_output_value");
		}
	}

	template <class T, class U> inline void solver<T, U>::put_training_output_value(unsigned int o_n, U v)
	{
		if (training_output_vector && o_n>=0 && o_n<training_output_vector->Length)
		{
			training_output_vector[o_n]=v;
		}
		else
		{
			throw std::out_of_range("\nError in solver<T, U>::put_training_output_value");
		}
	}

	template <class T, class U> inline U solver<T, U>::get_testing_output_value(unsigned int o_n)
	{
		if (testing_output_vector && o_n>=0 && o_n<testing_output_vector->Length)
		{
			return testing_output_vector[o_n];
		}
		else
		{
			throw std::out_of_range("\nError in solver<T, U>::get_testing_output_value");
		}
	}

	template <class T, class U> inline void solver<T, U>::put_testing_output_value(unsigned int o_n, U v)
	{
		if (testing_output_vector && o_n>=0 && o_n<testing_output_vector->Length)
		{
			testing_output_vector[o_n]=v;
		}
		else
		{
			throw std::out_of_range("\nError in solver<T, U>::put_testing_output_value");
		}
	}

	template <class T, class U> inline U solver<T, U>::get_output_value(unsigned int el)
	{
		if (get_parent_problem()->get_training_flag())
		{
			return get_training_output_value((get_parent_problem()->get_current_training_example()*get_parent_problem()->get_number_of_targets())+el);
		}
		else
		{
			return get_testing_output_value((get_parent_problem()->get_current_testing_example()*get_parent_problem()->get_number_of_targets())+el);
		}
	}
		
	template <class T, class U> inline void solver<T, U>::put_output_value(unsigned int el, U v)
	{
		if (get_parent_problem()->get_training_flag())
		{
			put_training_output_value((get_parent_problem()->get_current_training_example()*get_parent_problem()->get_number_of_targets())+el, v);
		}
		else
		{
			put_testing_output_value((get_parent_problem()->get_current_testing_example()*get_parent_problem()->get_number_of_targets())+el, v);
		}
	}

	template <class T, class U> inline unsigned int solver<T, U>::get_number_of_MLPs()
	{
		return number_of_MLPs;
	}

	template <class T, class U> inline void solver<T, U>::put_number_of_MLPs(unsigned int n_MLPs)
	{
		number_of_MLPs=n_MLPs;
	}

	template <class T, class U> inline MLP<T, U>% solver<T, U>::get_MLP(unsigned int MLP_n)
	{
		if (MLPs && MLP_n>=0 && MLP_n<MLPs->Length)
		{
			return *MLPs[MLP_n];
		}
		else
		{
			throw std::out_of_range("\nError in solver<T, U>::get_MLP");
		}
	}

	template <class T, class U> inline void solver<T, U>::put_MLPs()
	{
		try
		{
			MLPs=gcnew array<MLP<T, U>^>(get_number_of_MLPs());
			for (unsigned int MLP_n=0; MLP_n<get_number_of_MLPs(); MLP_n++)
			{
				MLPs[MLP_n]=gcnew MLP<T, U>(this, MLP_n);
			}
		}
		catch(const std::exception& error)
		{
			io_m.cerr_stream << error.what() << std::endl;
		}
	}

	// Worker Functions

	template <class T, class U> void solver<T, U>::load(System::String^ f_s)
	{
		pin_ptr<const wchar_t> file_spec=PtrToStringChars(f_s);
		std::wifstream file_stream;
		file_stream.open(file_spec);
		file_stream >> *this;
		file_stream.close();
	}

	template <class T, class U> void solver<T, U>::save(System::String^ f_s)
	{
		pin_ptr<const wchar_t> file_spec=PtrToStringChars(f_s);
		std::wofstream file_stream;
		file_stream.open(file_spec);
		file_stream << *this;
		file_stream.close();
	}

	template <class T, class U> void solver<T, U>::training(unsigned int MLP_n)
	{
		get_parent_problem()->put_training_flag();
		get_MLP(MLP_n).training();
	}

	template <class T, class U> void solver<T, U>::testing(unsigned int MLP_n)
	{
		get_parent_problem()->put_testing_flag();
		get_MLP(MLP_n).testing();
	}

	template <class T, class U> void solver<T, U>::allocate_storage()
	{
		training_output_vector=gcnew array<U>(get_parent_problem()->get_number_of_training_examples()*get_parent_problem()->get_number_of_targets());
		testing_output_vector=gcnew array<U>(get_parent_problem()->get_number_of_testing_examples()*get_parent_problem()->get_number_of_targets());
	}

	template <class T, class U> unsigned int solver<T, U>::normalise_output_vector()
	{
		U min_val;
		unsigned int min_el;
		get_min_output_val(min_val, min_el);
		U max_val;
		unsigned int max_el;
		get_max_output_val(max_val, max_el);
		U val_range=max_val - min_val;
		for(unsigned int el=0; el<get_parent_problem()->get_number_of_targets(); el++)
		{
			U old_val=get_output_value(el);
			U new_val=(old_val - min_val) / val_range;
			put_output_value(el, new_val);
		}
		return max_el;
	}

	template <class T, class U> void solver<T, U>::get_min_output_val(U& min_val, unsigned int& min_el)
	{
		min_val=std::numeric_limits<U>::max();
		min_el=std::numeric_limits<unsigned int>::max();
		for(unsigned int el=0; el<get_parent_problem()->get_number_of_targets(); el++)
		{
			U val=get_output_value(el);
			if (val < min_val)
			{
				min_val=val;
				min_el=el;
			}
		}
	}

	template <class T, class U> void solver<T, U>::get_max_output_val(U& max_val, unsigned int& max_el)
	{
		max_val=std::numeric_limits<U>::min();
		max_el=std::numeric_limits<unsigned int>::max();
		for(unsigned int el=0; el<get_parent_problem()->get_number_of_targets(); el++)
		{
			U val=get_output_value(el);
			if (val > max_val)
			{
				max_val=val;
				max_el=el;
			}
		}
	}

	// GUI Functions

	template <class T, class U> void solver<T, U>::display_solver_form(System::String^ progress_text, unsigned int progress_min, unsigned int progress_val, unsigned int progress_max)
	{
		if (!solver_form)
		{
			solver_form=gcnew SimpleGUI::Solver<T, U>(this);
		}
		solver_form->Show();
		solver_form->update_form(progress_text, progress_min, progress_val, progress_max);
	}

	template <class T, class U> void solver<T, U>::show_vectors()
	{
		if (input_form)
		{
			input_form->show_vector();
		}
		if (output_form)
		{
			output_form->show_vector();
		}
		if (target_form)
		{
			target_form->show_vector();
		}
	}

	template <class T, class U> void solver<T, U>::display_input_form()
	{
		if (!input_form)
		{
			input_form=gcnew SimpleGUI::Input<T, U>(this);
		}
		input_form->Show();
	}

	template <class T, class U> void solver<T, U>::display_output_form()
	{
		if (!output_form)
		{
			output_form=gcnew SimpleGUI::Output<T, U>(this);
		}
		output_form->Show();
	}

	template <class T, class U> void solver<T, U>::display_target_form()
	{
		if (!target_form)
		{
			target_form=gcnew SimpleGUI::Target<T, U>(this);
		}
		target_form->Show();
	}

	template <class T, class U> void solver<T, U>::invalidate_solver_form()
	{
		solver_form=nullptr;
	}

	template <class T, class U> void solver<T, U>::invalidate_input_form()
	{
		input_form=nullptr;
	}

	template <class T, class U> void solver<T, U>::invalidate_output_form()
	{
		output_form=nullptr;
	}

	template <class T, class U> void solver<T, U>::invalidate_target_form()
	{
		target_form=nullptr;
	}

	template <class T, class U> void solver<T, U>::display_MLPs()
	{
		for (unsigned int MLP_n=0; MLP_n<get_number_of_MLPs(); MLP_n++)
		{
			get_MLP(MLP_n).display_form();
		}
	}

	// Serialisation Functions

	template <class T, class U> void solver<T, U>::read(std::wifstream& in)
	{
		wchar_t title[500];
		in.getline(title, 500);
		put_title(gcnew System::String(title));
		unsigned int number_of_training_examples;
		in >> number_of_training_examples;
		get_parent_problem()->put_number_of_training_examples(number_of_training_examples);
		unsigned int number_of_testing_examples;
		in >> number_of_testing_examples;
		get_parent_problem()->put_number_of_testing_examples(number_of_testing_examples);
		unsigned int current_training_example;
		in >> current_training_example;
		get_parent_problem()->put_current_training_example(current_training_example);
		unsigned int current_testing_example;
		in >> current_testing_example;
		get_parent_problem()->put_current_testing_example(current_testing_example);
		unsigned int number_of_inputs;
		in >> number_of_inputs;
		get_parent_problem()->put_number_of_inputs(number_of_inputs);
		unsigned int number_of_targets;
		in >> number_of_targets;
		get_parent_problem()->put_number_of_targets(number_of_targets);
		unsigned int wrap_input;
		in >> wrap_input;
		get_parent_problem()->put_wrap_input(wrap_input);
		unsigned int wrap_target;
		in >> wrap_target;
		get_parent_problem()->put_wrap_target(wrap_target);
		unsigned int number_of_MLPs;
		in >> number_of_MLPs;
		put_number_of_MLPs(number_of_MLPs);
		for (unsigned int MLP_n=0; MLP_n<get_number_of_MLPs(); MLP_n++)
		{
			in >> get_MLP(MLP_n);
		}
	}

	template <class T, class U> void solver<T, U>::write(std::wofstream& out)
	{
		pin_ptr<const wchar_t> title=PtrToStringChars(get_title());
		wchar_t new_title[500];
		wcscpy_s(new_title, 500, title);
		out << new_title << std::endl;
		out << get_parent_problem()->get_number_of_training_examples() << " ";
		out << get_parent_problem()->get_number_of_testing_examples() << " ";
		out << get_parent_problem()->get_current_training_example() << " ";
		out << get_parent_problem()->get_current_testing_example() << " ";
		out << get_parent_problem()->get_number_of_inputs() << " ";
		out << get_parent_problem()->get_number_of_targets() << " ";
		out << get_parent_problem()->get_wrap_input() << " ";
		out << get_parent_problem()->get_wrap_target() << " ";
		out << get_number_of_MLPs() << " ";
		for (unsigned int MLP_n=0; MLP_n<get_number_of_MLPs(); MLP_n++)
		{
			out << get_MLP(MLP_n);
		}
	}

	template <class T, class U> void solver<T, U>::display(std::wofstream& out)
	{
		out << "Solver:" << std::endl;
		pin_ptr<const wchar_t> title=PtrToStringChars(get_title());
		wchar_t new_title[500];
		wcscpy_s(new_title, 500, title);
		out << "Title: " << new_title << std::endl;
		out << "Number of Training Examples: " << get_parent_problem()->get_number_of_training_examples() << std::endl;
		out << "Number of Testing Examples: " << get_parent_problem()->get_number_of_testing_examples() << std::endl;
		out << "Current Training Example: " << get_parent_problem()->get_current_training_example() << std::endl;
		out << "Current Testing Example: " << get_parent_problem()->get_current_testing_example() << std::endl;
		out << "Number of Inputs: " << get_parent_problem()->get_number_of_inputs() << std::endl;
		out << "Number of Targets: " << get_parent_problem()->get_number_of_targets() << std::endl;
		out << "Wrap Input: " << get_parent_problem()->get_wrap_input() << std::endl;
		out << "Wrap Target: " << get_parent_problem()->get_wrap_target() << std::endl;
		out << "Number of MLPs: " << get_number_of_MLPs() << std::endl;
		for (unsigned int MLP_n=0; MLP_n<get_number_of_MLPs(); MLP_n++)
		{
			out << get_MLP(MLP_n);
		}
	}

	template <class T, class U> std::wifstream& operator>>(std::wifstream& in, solver<T, U>% s)
	{
		if (in.good())
		{
			s.read(in);
			return in;
		}
		else
		{
			throw std::ios_base::failure("\nError in wifstream& >> solver<T, U>%");
		}
	}

	template <class T, class U> std::wofstream& operator<<(std::wofstream& out, solver<T, U>% s)
	{
		if (out.good())
		{
			s.write(out);
			return out;
		}
		else
		{
			throw std::ios_base::failure("\nError in wofstream& << solver<T, U>%");
		}
	}

	// Explicit Instantiation

	template ref class solver<bool, double>;
	template ref class solver<unsigned char, double>;
	template std::wifstream& operator>>(std::wifstream& in, solver<bool, double>% s);
	template std::wifstream& operator>>(std::wifstream& in, solver<unsigned char, double>% s);
	template std::wofstream& operator<<(std::wofstream& out, solver<bool, double>% s);
	template std::wofstream& operator<<(std::wofstream& out, solver<unsigned char, double>% s);
}
